package com.bolt.common.web;


import com.bolt.common.BoltConstants;
import com.bolt.common.convert.Convert;
import com.bolt.common.lang.Assert;
import com.bolt.common.utils.StrUtil;
import com.bolt.common.xss.XssSqlHttpServletRequestWrapper;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.UserAgent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;

import static com.bolt.common.BoltConstants.OR_SEPARATOR;

/**
 * HttpServletRequest帮助类
 */
public class RequestUtil {

    private static final Logger log = LoggerFactory.getLogger(RequestUtil.class);

    /**
     * 获取QueryString的参数，并使用URLDecoder以UTF-8格式转码。如果请求是以post方法提交的，
     * 那么将通过HttpServletRequest#getParameter获取。
     *
     * @param request web请求
     * @param name    参数名称
     * @return
     */
    public static String getQueryParam(HttpServletRequest request, String name) {
        HttpServletRequest requestWrapper = RequestUtil.getRequest(request);
        if (StrUtil.isBlank(name)) {
            return null;
        }
        if (requestWrapper.getMethod().equalsIgnoreCase(BoltConstants.POST)) {
            return requestWrapper.getParameter(name);
        }
        String s = requestWrapper.getQueryString();
        if (StrUtil.isBlank(s)) {
            return null;
        }
        try {
            s = URLDecoder.decode(s, BoltConstants.UTF8);
        } catch (UnsupportedEncodingException e) {
            log.error("encoding " + BoltConstants.UTF8 + " not support?", e);
        }
        String[] values = parseQueryString(s).get(name);
        if (values != null && values.length > 0) {
            return values[values.length - 1];
        } else {
            return null;
        }
    }

    /**
     * 取得带相同前缀的Request Parameters.
     * <p>
     * 返回的结果的Parameter名已去除前缀.
     */
    public static Map<String, String[]> getParametersStartingWith(HttpServletRequest request, String prefix, String suffix) {
        Assert.notNull(request, "Request must not be null");
        @SuppressWarnings("rawtypes")
        Enumeration paramNames = RequestUtil.getRequest(request).getParameterNames();
        Map<String, String[]> params = new TreeMap<String, String[]>();
        if (prefix == null) {
            prefix = "";
        }
        if (suffix == null) {
            suffix = "";
        }
        while (paramNames != null && paramNames.hasMoreElements()) {
            String paramName = (String) paramNames.nextElement();
            if (("".equals(prefix) || paramName.startsWith(prefix))
                    && ("".equals(suffix) || paramName.endsWith(suffix))) {
                String unprefixed = paramName.substring(prefix.length(), paramName.length() - suffix.length());
                String[] values = request.getParameterValues(paramName);
                if (values == null || values.length == 0) {
                    // Do nothing, no values found at all.
                } else if (values.length > 1) {
                    params.put(unprefixed, values);
                } else {
                    params.put(unprefixed, new String[]{values[0]});
                }
            }
        }
        return params;
    }


    @SuppressWarnings("unchecked")
    public static Map<String, String> getQueryParams(HttpServletRequest request) {
        HttpServletRequest requestWrapper = RequestUtil.getRequest(request);
        Map<String, String[]> map;
        if (request.getMethod().equalsIgnoreCase(BoltConstants.POST)) {
            map = requestWrapper.getParameterMap();
        } else {
            String s = requestWrapper.getQueryString();
            if (StrUtil.isBlank(s)) {
                return new HashMap<String, String>();
            }
            try {
                s = URLDecoder.decode(s, BoltConstants.UTF8);
            } catch (UnsupportedEncodingException e) {
                log.error("encoding " + BoltConstants.UTF8 + " not support?", e);
            }
            map = parseQueryString(s);
        }

        Map<String, String> params = new HashMap<String, String>(map.size());
        int len;
        for (Map.Entry<String, String[]> entry : map.entrySet()) {
            len = entry.getValue().length;
            if (len == 1) {
                params.put(entry.getKey(), entry.getValue()[0]);
            } else if (len > 1) {
                params.put(entry.getKey(), StrUtil.join(",", entry.getValue()));
            }
        }
        return params;
    }


    public static Map<String, String[]> parseQueryString(String s) {
        String valArray[] = null;
        if (s == null) {
            throw new IllegalArgumentException();
        }
        Map<String, String[]> ht = new HashMap<String, String[]>();
        StringTokenizer st = new StringTokenizer(s, "&");
        while (st.hasMoreTokens()) {
            String pair = (String) st.nextToken();
            int pos = pair.indexOf('=');
            if (pos == -1) {
                continue;
            }
            String key = pair.substring(0, pos);
            String val = pair.substring(pos + 1, pair.length());
            if (ht.containsKey(key)) {
                String oldVals[] = (String[]) ht.get(key);
                valArray = new String[oldVals.length + 1];
                for (int i = 0; i < oldVals.length; i++) {
                    valArray[i] = oldVals[i];
                }
                valArray[oldVals.length] = val;
            } else {
                valArray = new String[1];
                valArray[0] = val;
            }
            ht.put(key, valArray);
        }
        return ht;
    }

    public static Map<String, String> getRequestMap(HttpServletRequest request,
                                                    String prefix) {
        return getRequestMap(request, prefix, false);
    }

    public static Map<String, String> getRequestMapWithPrefix(
            HttpServletRequest request, String prefix) {
        return getRequestMap(request, prefix, true);
    }

    public static Map<String, Object> getRequestMap(HttpServletRequest req) {
        Map<String, Object> parameters = new HashMap<String, Object>();
        Enumeration<?> enumeration = req.getParameterNames();
        while (enumeration.hasMoreElements()) {
            Object obj = enumeration.nextElement();
            if (obj == null) {
                continue;
            }
            String name = obj.toString();
            String value = req.getParameter(name);
            if (name == null || value == null || name.startsWith("_")) {
                continue;
            }
            parameters.put(name, value);
        }
        return parameters;
    }

    @SuppressWarnings("unchecked")
    private static Map<String, String> getRequestMap(
            HttpServletRequest request, String prefix, boolean nameWithPrefix) {
        Map<String, String> map = new HashMap<String, String>();
        Enumeration<String> names = RequestUtil.getRequest(request).getParameterNames();
        String name, key, value;
        while (names.hasMoreElements()) {
            name = names.nextElement();
            if (name.startsWith(prefix)) {
                key = nameWithPrefix ? name : name.substring(prefix.length());
                value = StrUtil.join(",", request.getParameterValues(name));
                map.put(key, value);
            }
        }
        return map;
    }

    /**
     * 获取访问者IP
     * <p>
     * 在一般情况下使用Request.getRemoteAddr()即可，但是经过nginx等反向代理软件后，这个方法会失效。
     * <p>
     * 本方法先从Header中获取X-Real-IP，如果不存在再从X-Forwarded-For获得第一个IP(用,分割)，
     * 如果还不存在则调用Request .getRemoteAddr()。
     *
     * @param request
     * @return
     */
    public static String getRemoteIpAddress(HttpServletRequest request) {
        String ip = "";
         ip = request.getHeader("X-Real-IP");

        if (ip != null  && (ip.length() != 0) && !"unknown".equalsIgnoreCase(ip)) {
            return ip;
        }
        Enumeration<?> enumeration = request.getHeaderNames();
        while (enumeration.hasMoreElements()) {
            String paraName = (String) enumeration.nextElement();
            if ("x-forward-for".equalsIgnoreCase(paraName) || "x-forwarded-for".equalsIgnoreCase(paraName)) {
                ip = request.getHeader(paraName);
                break;
            }
        }
        String localIP = "127.0.0.1";
        if ((ip == null) || (ip.length() == 0) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if ((ip == null) || (ip.length() == 0) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if ((ip == null) || (ip.length() == 0) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }


    /**
     * 获得请求的session id，但是HttpServletRequest#getRequestedSessionId()方法有一些问题。
     * 当存在部署路径的时候，会获取到根路径下的jsessionid。
     *
     * @param request
     * @return
     * @see HttpServletRequest#getRequestedSessionId()
     */
    public static String getRequestedSessionId(HttpServletRequest request) {
        String sid = request.getRequestedSessionId();
        String ctx = request.getContextPath();
        // 如果session id是从url中获取，或者部署路径为空，那么是在正确的。
        if (request.isRequestedSessionIdFromURL() || StrUtil.isBlank(ctx)) {
            return sid;
        } else {
            // 手动从cookie获取
            String jsessionId = CookieUtil.getCookie(request, BoltConstants.JSESSION_COOKIE);
            if (jsessionId != null) {
                return jsessionId;
            } else {
                return null;
            }
        }
    }

    /* *
         * @Description 读取request 已经被防止XSS，SQL注入过滤过的 请求头key 对应的value
         * @Param [request, key]
         * @Return java.lang.String
         */
    public static String getHeader(HttpServletRequest request, String key) {
        return RequestUtil.getRequest(request).getHeader(key);
    }

    public static HttpServletRequest getRequest(HttpServletRequest request) {
        return new XssSqlHttpServletRequestWrapper(request);
    }

    /**
     * 从HTTP请求提取AccessTkoen
     *
     * @param request
     * @return
     */
    public static String getOAuthAccessToken(HttpServletRequest request) {
        return request.getHeader(BoltConstants.AUTH_HEADER);
    }


    public static int getQueryInt(HttpServletRequest request, String name, int defaultValue) {
        return Convert.toInt(getQueryParam(request, name), defaultValue);
    }

    public static long getQueryLong(HttpServletRequest request, String name, long defaultValue) {
        return Convert.toLong(getQueryParam(request, name), defaultValue);
    }

    public static Boolean getQueryBool(HttpServletRequest request, String name, Boolean defaultValue) {
        return Convert.toBool(getQueryParam(request, name), defaultValue);
    }


    public static Pageable buildPageableFromHttpRequest(HttpServletRequest request) {
        return buildPageableFromHttpRequest(request, null);
    }

    public static Pageable buildPageableFromHttpRequest(HttpServletRequest request, Sort sort) {
        int pageSize = RequestUtil.getQueryInt(request, BoltConstants.PAGE_SIZE, 20);

        int pageNumber = RequestUtil.getQueryInt(request, BoltConstants.PAGE_CURRENT_KEY, 1);
        if (sort == null) {
            sort = buildSortFromHttpRequest(request);
        }
        return PageRequest.of(pageNumber - 1, pageSize, sort);
    }

    public static Sort buildSortFromHttpRequest(HttpServletRequest request) {
        String sidx = StrUtil.isBlank(request.getParameter("sort")) ? "id" : request.getParameter("sort");
        Sort.Direction sord = "desc".equalsIgnoreCase(request.getParameter("order")) ? Sort.Direction.DESC : Sort.Direction.ASC;
        Sort sort = null;

        //按照逗号切分支持多属性排序
        for (String sidxItem : sidx.split(",")) {
            if (StrUtil.isNotBlank(sidxItem)) {
                //再按空格切分获取排序属性和排序方向
                String[] sidxItemWithOrder = sidxItem.trim().split(" ");
                String sortname = sidxItemWithOrder[0];
                //如果查询属性包含_OR_则取第一个作为排序属性
                //因此在写OR多属性查询时注意把排序属性写在最前面
                if (sortname.indexOf(OR_SEPARATOR) > -1) {
                    sortname = StrUtil.subBefore(sortname, OR_SEPARATOR, true);
                }
                //如果单个属性没有跟随排序方向，则取Grid组件传入的sord参数定义
                if (sidxItemWithOrder.length == 1) {
                    if (sort == null) {
                        //初始化排序对象
                        sort = Sort.by(sord, sortname);
                    } else {
                        //and追加多个排序
                        sort = sort.and(Sort.by(sord, sortname));
                    }
                } else {
                    //排序属性后面空格跟随排序方向定义
                    String sortorder = sidxItemWithOrder[1];
                    if (sort == null) {
                        sort = Sort.by("desc".equalsIgnoreCase(sortorder) ? Sort.Direction.DESC : Sort.Direction.ASC, sortname);
                    } else {
                        sort = sort.and(Sort.by("desc".equalsIgnoreCase(sortorder) ? Sort.Direction.DESC : Sort.Direction.ASC, sortname));
                    }
                }
            }
        }
        return sort;
    }


    public static String getBaseUrl(HttpServletRequest request) {
        String path = request.getContextPath();
        String defaultPath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
        return defaultPath;
    }

    /**
     * 是否是Ajax异步请求
     *
     * @param request
     */
    public static boolean isAjaxRequest(HttpServletRequest request) {
        String accept = request.getHeader("accept");
        if (accept != null && accept.indexOf("application/json") != -1) {
            return true;
        }

        String xRequestedWith = request.getHeader("X-Requested-With");
        if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1) {
            return true;
        }

        String uri = request.getRequestURI();
        if (StrUtil.endWithAny(uri, ".json", ".xml")) {
            return true;
        }

        String ajax = request.getParameter("__ajax");
        if (StrUtil.endWithAny(ajax, "json", "xml")) {
            return true;
        }
        return false;
    }

    /**
     * 判断User-Agent 是不是来自于手机
     */
    private final static String[] agent = {"Android", "iPhone", "iPod", "iPad", "Windows Phone", "MQQBrowser"};

    public static boolean checkAgentIsMobile(String ua) {
        boolean flag = false;
        if (!ua.contains("Windows NT") || (ua.contains("Windows NT") && ua.contains("compatible; MSIE 9.0;"))) {
            // 排除 苹果桌面系统
            if (!ua.contains("Windows NT") && !ua.contains("Macintosh")) {
                for (String item : agent) {
                    if (ua.contains(item)) {
                        flag = true;
                        break;
                    }
                }
            }
        }
        return flag;
    }

    public static String getBrowser(HttpServletRequest request) {
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        Browser browser = userAgent.getBrowser();
        return browser.getName();
    }

    /**
     * 下载文件名重新编码
     *
     * @param request  请求对象
     * @param fileName 文件名
     * @return 编码后的文件名
     */
    public static String getDownloadFileName(HttpServletRequest request, String fileName)
            throws UnsupportedEncodingException {
        final String agent = request.getHeader("USER-AGENT");
        String filename = fileName;
        if (agent.contains("MSIE")) {
            // IE浏览器
            filename = URLEncoder.encode(filename, "utf-8");
            filename = filename.replace("+", " ");
        } else if (agent.contains("Firefox")) {
            // 火狐浏览器
            filename = new String(fileName.getBytes(), "ISO8859-1");
        } else if (agent.contains("Chrome")) {
            // google浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        } else {
            // 其它浏览器
            filename = URLEncoder.encode(filename, "utf-8");
        }
        return filename;
    }

}
